module fiona_var_mod

#ifdef HAS_MPI
  use mpi
#endif
  use fiona_common_mod
  use fiona_dim_mod

  implicit none

  type var_dim_type
    type(dim_type), pointer :: ptr => null()
  end type var_dim_type

  type var_type
    integer id
    character(30) name
    character(256) long_name
    character(60) units
    logical :: has_unlimited_dim = .false.
    integer dtype
    integer(2), pointer :: i2_missing_value => null()
    integer(4), pointer :: i4_missing_value => null()
    integer(8), pointer :: i8_missing_value => null()
    real(4), pointer :: r4_missing_value => null()
    real(8), pointer :: r8_missing_value => null()
    type(var_dim_type), allocatable :: dims(:)
    type(hash_table_type) atts
#ifdef HAS_MPI
    integer :: send_req = MPI_REQUEST_NULL
#endif
  contains
    procedure :: init => var_init
    procedure :: clear => var_clear
    procedure :: wait_send => var_wait_send
    final :: var_final
  end type var_type

contains

  subroutine var_init(this, name, ndim, long_name, units, dtype, id, missing_value)

    class(var_type), intent(inout) :: this
    character(*), intent(in) :: name
    integer, intent(in) :: ndim
    character(*), intent(in), optional :: long_name
    character(*), intent(in), optional :: units
    character(*), intent(in), optional :: dtype
    integer, intent(in), optional :: id
    class(*), intent(in), optional :: missing_value

    real real

    call this%clear()

    this%name = name
    if (present(long_name)) this%long_name = long_name
    if (present(units)) this%units = units
    if (present(id)) this%id = id

    if (.not. present(dtype)) then
      select case (sizeof(real))
      case (4)
        this%dtype = NF90_FLOAT
      case (8)
        this%dtype = NF90_DOUBLE
      end select
    else
      select case (dtype)
      case ('real', 'real(4)', 'r4')
        this%dtype = NF90_FLOAT
      case ('real(8)', 'r8')
        this%dtype = NF90_DOUBLE
      case ('integer(2)', 'i2')
        this%dtype = NF90_SHORT
      case ('integer', 'integer(4)', 'i4')
        this%dtype = NF90_INT
      case ('integer(8)', 'i8')
        this%dtype = NF90_INT64
      case ('char', 'character', 'c', 'str', 'string', 's')
        this%dtype = NF90_CHAR
      case default
        call log_error('Unknown data type ' // trim(dtype) // ' for variable ' // trim(name) // '!')
      end select
    end if

    if (present(missing_value)) then
      select type (missing_value)
      type is (integer(2))
        allocate(this%i2_missing_value)
        this%i2_missing_value = missing_value
      type is (integer(4))
        allocate(this%i4_missing_value)
        this%i4_missing_value = missing_value
      type is (integer(8))
        allocate(this%i8_missing_value)
        this%i8_missing_value = missing_value
      type is (real(4))
        allocate(this%r4_missing_value)
        this%r4_missing_value = missing_value
      type is (real(8))
        allocate(this%r8_missing_value)
        this%r8_missing_value = missing_value
      class default
        call log_error('Unknown missing_value type!', __FILE__, __LINE__)
      end select
    end if

    allocate(this%dims(ndim))
    call this%atts%init()

  end subroutine var_init

  subroutine var_clear(this)

    class(var_type), intent(inout) :: this

    if (associated(this%i4_missing_value)) deallocate(this%i4_missing_value)
    if (associated(this%i8_missing_value)) deallocate(this%i8_missing_value)
    if (associated(this%r4_missing_value)) deallocate(this%r4_missing_value)
    if (associated(this%r8_missing_value)) deallocate(this%r8_missing_value)
    if (allocated(this%dims)) deallocate(this%dims)
    call this%atts%clear()
#ifdef HAS_MPI
    this%send_req = MPI_REQUEST_NULL
#endif

  end subroutine var_clear

  subroutine var_wait_send(this)

    class(var_type), intent(inout) :: this

    integer ierr

#ifdef HAS_MPI
    if (this%send_req /= MPI_REQUEST_NULL) then
      call MPI_WAIT(this%send_req, MPI_STATUS_IGNORE, ierr)
      this%send_req = MPI_REQUEST_NULL
    end if
#endif

  end subroutine var_wait_send

  subroutine var_final(this)

    type(var_type), intent(inout) :: this

    call this%clear()

  end subroutine var_final

end module fiona_var_mod
